
#ifndef _TTG_EXTRAS_DYNAMICFIELD_
#define _TTG_EXTRAS_DYNAMICFIELD_
#include "Common/Definitions.h"

#include <vector>
#include "CalculatorCore.h"
#include "Common/Structures.h"

//Definitions of used class.
/*namespace ttg
{
	namespace extras
	{
		template <class T>
		class DynamicField;
	}
}*/

namespace ttg_internal
{
	//Base class for field binders. Will be used by DynamicField.
	class FieldBinder
	{
		protected:
			//Must be called by DynamicField::update() only.
			virtual void onChange(bool isfirst) = 0;
		public:
			virtual ~FieldBinder()
					{ /*nothing*/ }
			friend class FieldBinderUpdater;
	};

	//Auxiliary class, which calls FieldBinder::onChange().
	//It uses to avoid declaration of template friend from another namespace.
	class FieldBinderUpdater
	{
		public:
			//Calls binder->onChange(isfirst)
			static inline void onChange(FieldBinder *binder, bool isfirst)
			{ binder->onChange(isfirst); }
	};
}

namespace ttg
{
	namespace extras
	{
		typedef ttg_internal::FieldBinder FieldBinder;
		typedef ttg_internal::FieldBinderUpdater FieldBinderUpdater;

		//Represents dynamic field (field, which value will be evaluated from the string).
		template <class T>
		class DynamicField
		{
			private:
				Operation<T> *dvalue;		//dynamic value
				T svalue;					//static value
				const T *rvalue;			//reference value
				bool isstatic;
				bool isref;
				bool shouldrelease;
				std::vector<structures::Pair<ttg_internal::FieldBinder *, bool>> binders;
			private:
				//Adds binder to list. It will be notified on each update.
				inline void addBinder(ttg_internal::FieldBinder *binder, bool isfirst)
				{
					binders.push_back(TTGPair<ttg_internal::FieldBinder *, bool>(binder, isfirst));
				}
				//Removes binder from the list.
				inline void removeBinder(ttg_internal::FieldBinder *binder)
				{
					vector<TTGPair<ttg_internal::FieldBinder *, bool>>::iterator it = binders.begin();
					while (it != binders.end())
					{
						if (it->getFirst() == binder)
							it = binders.erase(it);
						else
							it++;
					}
				}
				//Calls onChange for all binders.
				inline void updateBinders()
				{
					for (size_t i = 0; i < binders.size(); i++)
						ttg_internal::FieldBinderUpdater::onChange(binders[i].getFirst(), binders[i].getSecond());
				}
			public:
				DynamicField()
					:svalue()
				{
					this->dvalue = NULL;
					this->rvalue = NULL;
					this->isstatic = true;
					this->isref = false;
					this->shouldrelease = false;
				}
				//Returns true if field cannot change value before set() call.
				inline bool isStatic() const
					{ return isstatic; }
				//Sets dynamic value.
				void set(Operation<T> *value, bool shouldrelease = false)
				{
		#ifdef TTG_DEBUG
					if (value == NULL)
						throw Error(Error::FATAL, "DynamicField", "set",
									"value is equal to NULL");
		#endif
					if (this->shouldrelease && this->dvalue != NULL)
						delete this->dvalue;
					//Trying replace by static value.
					if (value->isStatic())
					{
						this->svalue = value->calculate();
						this->isstatic = true;
						this->isref = false;
						if (shouldrelease)
							delete value;
						this->updateBinders();
					}
					else	//no, it's true dynamic value.
					{
						this->dvalue = value;
						this->shouldrelease = shouldrelease;
						this->isref = false;
						this->isstatic = false;
					}
				}
				//Sets dynamic value.
				//In case of error throws Error().
				void set(const char *value, Params *params)
				{
					Operation<T> *op = Calculator<T>::create(value, params);
					if (op == NULL)
						throw Error(Error::FATAL, "DynamicField", "set",
									"Cannot create operation", value);
					this->set(Calculator<T>::create(value, params), true);
					this->updateBinders();
				}
				//Sets static value.
				void set(const T &value)
				{
					this->svalue = value;
					this->isstatic = true;
					this->isref = false;
					this->updateBinders();
				}
				//Sets reference value.
				//Note: destination should be avaible during each update()/get().
				void set(const T *value)
				{
					this->rvalue = value;
					this->isstatic = false;
					this->isref = true;
					this->updateBinders();
				}
				//Recalculates value.
				void update()
				{
					if (!isstatic && dvalue != NULL)
					{
						svalue = dvalue->calculate();
						this->updateBinders();
					}
				}
				//Returns current value (from last update).
				inline const T &get() const
				{
					return isref ? *rvalue : svalue;
				}
				~DynamicField()
				{
					if (shouldrelease)
						SAFE_DELETE(dvalue);
		#ifdef TTG_DEBUG
					if (binders.size() != 0)
						throw Error(Error::FATAL, "DynamicField", "~DynamicField",
									"All binders must be released before fields.");
		#endif
				}
			template<class T1, class T2> friend class TwoWayFieldBinder;
		};

		//Allows to bind two values in two-way mode.
		template <class T1, class T2>
		class TwoWayFieldBinder :public ttg_internal::FieldBinder
		{
			private:
				DynamicField<T1> *f1;
				DynamicField<T2> *f2;
				bool locker;			//to avoid updating by binder.
			protected:
				//Will be called by DynamicField::update().
				virtual void onChange(bool isfirst)
				{
					if (!locker)
					{
						locker = true;
						if (isfirst)
							f2->set(this->convertDirect(f1->get()));
						else
							f1->set(this->convertBack(f2->get()));
						locker = false;
					}
				}
				//Converts from T1 to T2.
				virtual T2 convertDirect(const T1 &val) = 0;
				//Converts from T2 to T1.
				virtual T1 convertBack(const T2 &val) = 0;
			public:
				//Creates FieldBinder.
				//Note: all fields must be avaible during binder live.
				TwoWayFieldBinder(DynamicField<T1> *f1, DynamicField<T2> *f2)
				{
					this->f1 = f1;
					f1->addBinder(this, true);
					this->f2 = f2;
					f2->addBinder(this, false);
					this->locker = false;
				}
				virtual ~TwoWayFieldBinder()
				{
					f1->removeBinder(this);
					f2->removeBinder(this);
				}
		};
	}
}

#endif
